#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include "m_global.h"
#include "m_staff.h"


#define CHECK_ALL_CNF   1

static cnf_file* init_new_file_node(void);
static void free_file_node(cnf_file *file_node);
static cnf_file* cnf_file_search(cnf_file *cnf_file_list, char *file_name);
static cnf_file* cnf_file_strict_search(cnf_file *cnf_file_list,
        char *file_name, enum FILE_TYPE file_type);
static cnf_file* cnf_file_search_end(cnf_file *cnf_file_list);
static void cnf_file_add(cnf_file *cnf_file_list, cnf_file *file);
static cnf_file* file_node_init(int fd, char *file_name, enum FILE_TYPE file_type);

static staff* new_member(void);
static void free_member(staff *member);
static void staff_list_init(staff *staff_list);
static staff* staff_search_member(staff *staff_list,
        char *name, char *email, char *mobile);
static staff* staff_search_end(staff *staff_list);
static void staff_add(staff *staff_list, staff *member);


/* *****************************************************************************
 * STAFF STRUCTURE INITIALIZE
 ******************************************************************************/
/* 1. parse CACHE FILE. 
 * 2. put parse result into staff_list.
 * 3. update staff type.
 * 4. search support.
 */
static staff* new_member(void)
{
    staff *new_member;
    new_member = (staff *) malloc(sizeof (staff));
    if (NULL == new_member)
        fprintf(stderr, "Function: new_member, malloc error.\n");
    else
        new_member->next = NULL;
    return new_member;
}

static void free_member(staff *member)
{
    if (member)
        free(member);
    member = NULL;
}

/* Called it before used list */
static void staff_list_init(staff *staff_list)
{
    if (staff_list)
        staff_list = NULL;
    staff_list->next = NULL;
    return;
}

static staff* staff_search_member(staff *staff_list,
        char *name, char *email, char *mobile)
{
    staff *staff_list_head = staff_list, *result = NULL;
    char *name_tmp = NULL, *email_tmp = NULL, *mobile_tmp = NULL;
    int search_case = 3;
    if (NULL == name) search_case--;
    else name_tmp = strtrim(name);
    if (NULL == email) search_case--;
    else email_tmp = strtrim(email);
    if (NULL == mobile) search_case--;
    else mobile_tmp = strtrim(mobile);
    if (0 == search_case) return NULL;
    search_case = 3 - search_case;

    for (result = staff_list_head; result; result = result->next)
    {
        if (search_case ==
                ((strcmp(name_tmp, result->name)
                + strcmp(name_tmp, result->email)
                + strcmp(name_tmp, result->mobile))))
            break;
    }

    return result;
}

static staff* staff_search_end(staff *staff_list)
{
    staff *last_member;
    for (last_member = staff_list; last_member->next; last_member = staff_list->next)
    {
        /* empty */
    }
    return last_member;
}

static void staff_add(staff *staff_list, staff *member)
{
    staff *last_member;
    last_member = staff_search_end(staff_list);
    if (NULL == last_member)
        last_member = member; /* List is null, last_member is first member. */
    else
        last_member->next = member;
    return;
}


/* *****************************************************************************
 * CNF FILE CACHE.
 ******************************************************************************/
/* 1. open all config files, cache file sock content. 
 * 2. read and put data into cnf_file list table.
 * 3. check config file type, update cnf_file file_type.
 * 4. add a function to print and check all cnf file format.
 */
static cnf_file* init_new_file_node(void)
{
    cnf_file *new_file;
    new_file = (cnf_file *) malloc(sizeof (cnf_file));
    if (NULL == new_file)
        fprintf(stderr, "Function: new_file_node, malloc error.\n");
    else
        new_file->next = NULL;
    return new_file;
}

static void free_file_node(cnf_file *file_node)
{
    if (file_node->file_name)
    {
        free(file_node->file_name);
        file_node->file_name = NULL;
    }
    if (file_node->text)
    {
        free(file_node->text);
        file_node->text = NULL;
    }
    if (file_node->next)
        file_node->next = NULL; /* Don't free */
    if (file_node)
    {
        free(file_node);
        file_node = NULL;
    }
}

/* Called it before used list */
void cnf_file_list_init(cnf_file *cnf_file_list)
{
    if (NULL != cnf_file_list)
    {
        fprintf(stderr, "BUG, cnf_file_list_init set no null list to null.\n");
        cnf_file_list = NULL;
    }
    return;
}

static cnf_file* cnf_file_search(cnf_file *cnf_file_list, char *file_name)
{
    cnf_file *cnf_file_list_head = cnf_file_list, *result = NULL;
    if (NULL == file_name) return NULL;

    for (result = cnf_file_list_head; result; result = result->next)
    {
        if (!strcmp(result->file_name, file_name))
            break;
    }

    return result;
}

static cnf_file* cnf_file_strict_search(cnf_file *cnf_file_list,
        char *file_name, enum FILE_TYPE file_type)
{
    cnf_file *cnf_file_list_head = cnf_file_list, *result = NULL;
    if (NULL == file_name) return NULL;

    for (result = cnf_file_list_head; result; result = result->next)
    {
        if (!strcmp(result->file_name, file_name) && result->file_type == file_type)
            break;
    }

    return result;
}

static cnf_file* cnf_file_search_end(cnf_file *cnf_file_list)
{
    cnf_file *last_file;
    for (last_file = cnf_file_list; last_file->next; last_file = cnf_file_list->next)
    {
        /* empty */
    }
    return last_file;
}

static void cnf_file_add(cnf_file *cnf_file_list, cnf_file *file)
{
    cnf_file *last_file;
    last_file = cnf_file_search_end(cnf_file_list);
    if (NULL == last_file)
        last_file = file; /* List is null, last_member is first member. */
    else
        last_file->next = file;
    return;
}

static cnf_file* file_node_init(int fd, char *file_name, enum FILE_TYPE file_type)
{
    cnf_file *new_file_node;
    new_file_node = init_new_file_node();
    if (NULL == new_file_node)
    {
        fprintf(stderr, "Function:put_fd_to_list error.\n");
        return NULL;
    }
    new_file_node->fd = fd;
    new_file_node->file_type = file_type;

    if (NULL == file_name) return new_file_node;

    new_file_node->text = (char *) malloc(strlen(file_name));
    if (NULL == new_file_node->text)
    {
        fprintf(stderr, "Function:put_fd_to_list malloc error.\n");
        return NULL;
    }
    if (sprintf(new_file_node->text, "%s", file_name) == strlen(file_name))
        return new_file_node;
    else
        free_file_node(new_file_node);
    return NULL;
}

static char* cache_file_contents(int fd)
{
    int read_length, length_count = 0, cache_file_length = 0x4000;
    char *buff, *pos, tmp[256];
    
    buff = (char *) malloc(cache_file_length); /* 16k */
    if (NULL == buff)
    {
        fprintf(stderr, "Function:cache_file_contents, malloc buffer error.\n");
        return NULL;
    }
    memset(buff, 0, cache_file_length);

    pos = &buff[0];
    while ((read_length = m_read(fd, tmp, 256)))
    {
        if (-1 == read_length)
            return NULL;
        strncpy(pos, tmp, read_length);
        pos += read_length;
        length_count += read_length;
        /* realloc */
        if ((length_count + 256) > cache_file_length)
        {
            cache_file_length += 256;
            buff = m_realloc(buff, cache_file_length);
            if ( NULL == buff)
                return NULL;
            pos = &buff[0];
            pos += length_count;
        }
    }
    return buff;
}

/* Function: read_file_into_list
 * Descrtiption:
 *      1. open file, put file sock into cnf_file list.
 *      2. read file content into cnf_list.
 *      3. initialize file configration type into cnf_list.
 * Return: 
 *      0 ok, nozero error.
 */
int read_file_into_list(cnf_file *config_file_list, char *file_name, 
        enum FILE_TYPE cnf_type)
{
    int fd;
    cnf_file *ready_node;
    
    fd = m_open_file(file_name, O_RDONLY);
    if ( -1 == fd)
    {
        fprintf(stderr, "Function:read_file_into_list, open file error.\n");
        return 1;
    }
    
    ready_node = file_node_init(fd, file_name, cnf_type);
    if (NULL == ready_node)
    {
        fprintf(stderr, "Function: read_file_into_list, ready_node is null.\n");
        return 1;
    }
    
    ready_node->text = cache_file_contents(fd);
    if(NULL == ready_node->text)
    {
        fprintf(stderr, "Cache %s failed.\n", file_name);
        return 1;
    }
    cnf_file_add(config_file_list, ready_node);
    return 0;
}

/* Function: read_file_into_list
 * Descrtiption:
 *      check your change correct.
 * Return: 
 *      0 ok, nozero error.
 */
int scan_and_print_all_cnf(char *m_general_config_dir)
{
    DIR *p_dir;
    struct dirent *p_dirent;
    if ((p_dir = opendir(m_general_config_dir)) == NULL)
    {
        fprintf(stderr, "Open directory %s failed, see follow informations.\n", m_general_config_dir);
        switch (errno)
        {
            case EACCES:
                fprintf(stderr, "Permission denied.\n");
                break;
            case EMFILE:
                fprintf(stderr, "Too many file descriptors in use by process.\n");
                break;
            case ENFILE:
                fprintf(stderr, "Too many files are currently open in the system.\n");
                break;
            case ENOENT:
                fprintf(stderr, "Directory does not exist, or name is an empty string.\n");
                break;
            case ENOMEM:
                fprintf(stderr, "Insufficient memory to complete the operation.\n");
                break;
            case ENOTDIR:
                fprintf(stderr, "name is not a directory.\n");
                break;
            default:
                break;
        }
        return 1;
    }

    while ((p_dirent = readdir(p_dir)))
    {
        fprintf(stdout, "%s\n", p_dirent->d_name);
    }

    if (0 == errno)
    {
        closedir(p_dir);
        if (errno == EBADF)
        {
            fprintf(stderr, "Invalid directory stream descriptor dir.\n");
            return 1;
        }
        return 0;
    }
    return 1;
}

